package org.hackystat.ui.projectviewer.gwt.client.dynatable;

import org.hackystat.ui.projectviewer.gwt.client.dataprovider.DataProviderMediator;
import org.hackystat.ui.projectviewer.gwt.client.dynatable.DynaTableDataProvider.RowDataAcceptor;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.InvocationException;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * A composite Widget that implements the main interface for the dynamic table, including the data
 * table, status indicators, and paging buttons.
 *
 * @author Pavel Senin, copied from Google GWT examples.
 */
public class DynaTableWidget extends Composite {

  /**
   * A dialog box for displaying an error.
   */
  private static class ErrorDialog extends DialogBox implements ClickListener {
    private HTML body = new HTML("");

    /**
     * Constructor.
     */
    public ErrorDialog() {
      setStylePrimaryName("DynaTable-ErrorDialog");
      Button closeButton = new Button("Close", this);
      VerticalPanel panel = new VerticalPanel();
      panel.setSpacing(4);
      panel.add(body);
      panel.add(closeButton);
      panel.setCellHorizontalAlignment(closeButton, VerticalPanel.ALIGN_RIGHT);
      setWidget(panel);
    }

    /**
     * Reports error message body.
     *
     * @return error message body.
     */
    public String getBody() {
      return body.getHTML();
    }

    /**
     * {@inheritDoc}
     */
    public void onClick(Widget sender) {
      hide();
    }

    /**
     * Sets the message body.
     *
     * @param html The body message to set.
     */
    public void setBody(String html) {
      body.setHTML(html);
    }
  }

  /**
   * Navigation bar widget.
   *
   * @author Pavel Senin.
   *
   */
  private class NavBar extends Composite implements ClickListener {

    public final DockPanel bar = new DockPanel();
    public final Button gotoFirst = new Button("&lt;&lt;", this);
    public final Button gotoNext = new Button("&gt;", this);
    public final Button gotoPrev = new Button("&lt;", this);
    public final HTML status = new HTML();
    public final int maxRows = 100;

    /**
     * Constructor.
     */
    public NavBar() {
      initWidget(bar);
      bar.setStyleName("navbar");
      status.setStyleName("status");

      HorizontalPanel buttons = new HorizontalPanel();
      buttons.add(gotoFirst);
      buttons.add(gotoPrev);
      buttons.add(gotoNext);
      bar.add(buttons, DockPanel.EAST);
      bar.setCellHorizontalAlignment(buttons, DockPanel.ALIGN_RIGHT);
      bar.add(status, DockPanel.CENTER);
      bar.setVerticalAlignment(DockPanel.ALIGN_MIDDLE);
      bar.setCellHorizontalAlignment(status, HasAlignment.ALIGN_RIGHT);
      bar.setCellVerticalAlignment(status, HasAlignment.ALIGN_MIDDLE);
      bar.setCellWidth(status, "100%");

      // Initialize prev & first button to disabled.
      //
      gotoPrev.setEnabled(false);
      gotoFirst.setEnabled(false);
    }

    /**
     * {@inheritDoc}
     */
    public void onClick(Widget sender) {
      if (sender.equals(gotoNext)) {
        startRow += getDataRowCount();
        if (startRow >= maxRows) {
          startRow = 0;
        }
        refresh();
      }
      else if (sender.equals(gotoPrev)) {
        startRow -= getDataRowCount();
        if (startRow < 0) {
          startRow = 0;
        }
        refresh();
      }
      else if (sender.equals(gotoFirst)) {
        startRow = 0;
        refresh();
      }
    }
  }

  /**
   * Row data acceptor (validator) implementation.
   *
   * @author Pavel Senin.
   *
   */
  private class RowDataAcceptorImpl implements RowDataAcceptor {
    /**
     * {@inheritDoc}
     */
    public void accept(int startRow, String[][] data) {

      int destRowCount = getDataRowCount();
      int destColCount = grid.getCellCount(0);
      assert (data.length <= destRowCount) : "Too many rows";

      int srcRowIndex = 0;
      int srcRowCount = data.length;
      int destRowIndex = 1; // skip navbar row
      for (; srcRowIndex < srcRowCount; ++srcRowIndex, ++destRowIndex) {
        String[] srcRowData = data[srcRowIndex];
        assert (srcRowData.length == destColCount) : " Column count mismatch";
        for (int srcColIndex = 0; srcColIndex < destColCount; ++srcColIndex) {
          String cellHTML = srcRowData[srcColIndex];
          grid.setText(destRowIndex, srcColIndex, cellHTML);
        }
      }

      // Clear remaining table rows.
      //
      boolean isLastPage = false;
      for (; destRowIndex < destRowCount + 1; ++destRowIndex) {
        isLastPage = true;
        for (int destColIndex = 0; destColIndex < destColCount; ++destColIndex) {
          grid.clearCell(destRowIndex, destColIndex);
        }
      }

      // Synchronize the nav buttons.
      navbar.gotoNext.setEnabled(!isLastPage);
      navbar.gotoFirst.setEnabled(startRow > 0);
      navbar.gotoPrev.setEnabled(startRow > 0);

      // Update the status message.
      //
      setStatusText((startRow + 1) + " - " + (startRow + srcRowCount));

      navbar.bar.remove(waitImage);
    }

    /**
     * {@inheritDoc}
     */
    public void failed(Throwable caught) {
      setStatusText("Error");
      if (errorDialog == null) {
        errorDialog = new ErrorDialog();
      }
      if (caught instanceof InvocationException) {
        errorDialog.setText("An RPC server could not be reached");
        errorDialog.setBody(NO_CONNECTION_MESSAGE);
      }
      else {
        errorDialog.setText("Unexcepted Error processing remote call");
        errorDialog.setBody(caught.getMessage());
      }
      errorDialog.center();
    }
  }

  private static final String NO_CONNECTION_MESSAGE = "<p> In order for this widget to "
      + "successfully display data, the server component must be available.</p>"
      + "<p>If you see this message , the server "
      + "component may not be available to respond to the RPC requests from "
      + "widget. Contact Hackystat develpers for more information.";

  /** {@literal} SMALL_WAIT_IMAGE icon. */
  public static final String SMALL_WAIT_IMAGE = "images/wait-animated-16.gif";
  final Image waitImage = new Image(SMALL_WAIT_IMAGE);

  private final RowDataAcceptor acceptor = new RowDataAcceptorImpl();

  private final Grid grid = new Grid();

  private final NavBar navbar = new NavBar();

  private ErrorDialog errorDialog = null;

  private final DockPanel outer = new DockPanel();

  private int startRow = 0;

  private String projectName;

  private String projectOwner;

  /**
   * Constructor.
   *
   * @param columns Columns headers.
   * @param columnStyles Columns CSS style names.
   * @param rowCount The row amount to display.
   */
  public DynaTableWidget(String[] columns, String[] columnStyles, int rowCount) {
    if (columns.length == 0) {
      throw new IllegalArgumentException("expecting a positive number of columns");
    }

    if (columnStyles != null && columns.length != columnStyles.length) {
      throw new IllegalArgumentException("expecting as many styles as columns");
    }

    waitImage.setStyleName("wait-image");
    initWidget(outer);
    grid.setStyleName("table");
    outer.add(navbar, DockPanel.NORTH);
    outer.add(grid, DockPanel.CENTER);
    initTable(columns, columnStyles, rowCount);
    setStyleName("DynaTable-DynaTableWidget");
  }

  /**
   * Sets project name.
   *
   * @param projectName The project name.
   * @param projectOwner The project owner.
   */
  public void setProjectName(String projectName, String projectOwner) {
    GWT.log("Project " + projectName + " owner " + projectOwner + " set in dynatable", null);
    this.projectName = projectName;
    this.projectOwner = projectOwner;
  }

  /**
   * Clears status text.
   */
  public void clearStatusText() {
    navbar.status.setHTML("&nbsp;");
  }

  /**
   * Refreshes the widget.
   */
  public void refresh() {
    // Disable buttons temporarily to stop the user from running off the end.
    //
    navbar.gotoFirst.setEnabled(false);
    navbar.gotoPrev.setEnabled(false);
    navbar.gotoNext.setEnabled(false);

    setStatusImage(waitImage);
    setStatusText("Please wait...");
    if (Log.isDebugEnabled()) {
      Log.debug("PV-DT: Firing updateRowData in dynatable");
    }
    if (DataProviderMediator.getInstance().lock()) {
      DataProviderMediator.getInstance().updateRowData(startRow, grid.getRowCount() - 1,
          projectName, projectOwner, acceptor);
    }
    else {
      Window.alert("RPC is locked, unable to proceed with communications. \n Please provide "
          + "feedback to hackystat-dev@googlegroups.com.");
    }
  }

  /**
   * Sets the status image into bar.
   *
   * @param image The image to set.
   */
  private void setStatusImage(Image image) {
    navbar.bar.add(image, DockPanel.WEST);
  }

  /**
   * Sets row count.
   *
   * @param rows The row count to set.
   */
  public void setRowCount(int rows) {
    grid.resizeRows(rows);
  }

  /**
   * Sets status text in the header.
   *
   * @param text The text to set.
   */
  public void setStatusText(String text) {
    navbar.status.setText(text);
  }

  /**
   * Reports row count.
   *
   * @return The row count.
   */
  private int getDataRowCount() {
    return grid.getRowCount() - 1;
  }

  /**
   * Inits table itself.
   *
   * @param columns The columns count.
   * @param columnStyles The columns styles.
   * @param rowCount The row count.
   */
  private void initTable(String[] columns, String[] columnStyles, int rowCount) {
    // Set up the header row. It's one greater than the number of visible rows.
    //
    grid.resize(rowCount + 1, columns.length);
    for (int i = 0, n = columns.length; i < n; i++) {
      grid.setText(0, i, columns[i]);
      if (columnStyles != null) {
        grid.getCellFormatter().setStyleName(0, i, columnStyles[i] + " header");
      }
    }
  }

  /**
   * Cleans the table content.
   */
  public void clean() {
    acceptor.accept(0, new String[0][4]);
    startRow = 0;
  }
}
